4. 컨테이너 실행
4.6 runc를 사용한 컨테이너 실행
runc란?
runc는 OCI Runtime Specification을 준수하도록 개발된 저수준 컨테이너 런타임. runc 바이너리의 서브 명령어로 기능을 구현하고, 상위 도구(고수준 런타임)에서는 runc를 서브 명령어와 함께 실행해서 컨테이너 생성, 시작, 정지, 삭제 등의 조작을 수행함
- runc: OCI 표준을 구현한 참조 저수준 런타임. Go 언어로 작성되었으며, 원래 Docker의 libcontainer에서 분리되어 OCI에 기증됨
- OCI (Open Container Initiative): 컨테이너 형식과 런타임에 대한 개방형 산업 표준 제정 단체
- 바이너리(Binary): 컴퓨터가 직접 실행할 수 있는 기계어 코드 파일. 예: runc 실행 파일
- 서브 명령어(Subcommand): 주 명령어 뒤에 붙는 하위 명령어. 예: runc run, runc create, runc start
컨테이너 실행 흐름:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[고수준 런타임] (containerd, CRI-O)
│
│ OCI Runtime Spec
↓
[runc] (저수준 런타임)
│
│ 시스템 콜
↓
[호스트 커널]
├─ Namespace (격리)
├─ cgroups (자원 제한)
└─ seccomp (시스템 콜 필터)
│
↓
[컨테이너 프로세스]
4.6.1 컨테이너 이미지를 가져오고 컨테이너 기반 작성
파일시스템 번들이란?
파일시스템 번들(Filesystem Bundle)은 runc가 컨테이너를 생성하는 데 필요한 모든 데이터를 저장하는 디렉터리. 다음 두 가지 요소로 구성됨
파일시스템 번들 구성요소:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Filesystem Bundle]
│
├─ config.json ← 컨테이너 실행 환경 설정 파일
│ │
│ ├─ ociVersion : OCI 버전 정보
│ ├─ process : 실행할 프로세스 정보
│ │ ├─ args : 실행 명령어와 인수
│ │ ├─ env : 환경 변수
│ │ ├─ cwd : 작업 디렉터리 (Current Working Directory)
│ │ └─ user : 실행 사용자 (uid/gid)
│ │
│ ├─ root : 루트 파일시스템 경로
│ ├─ hostname : 컨테이너 호스트명
│ │
│ └─ linux : Linux 전용 설정
│ ├─ namespaces : 격리할 Namespace 목록
│ ├─ resources : cgroup 자원 제한
│ └─ seccomp : 시스템 콜 필터링 설정
│
└─ rootfs/ ← 컨테이너 루트 파일시스템
├─ bin/ (컨테이너 내부에서 /로 보임)
├─ etc/
├─ lib/
├─ usr/
└─ ...
- 루트 파일시스템(rootfs): 컨테이너가
/로 인식하는 최상위 디렉터리 구조. 호스트 파일시스템과 분리된 컨테이너 전용 파일시스템 - config.json: 컨테이너 실행 환경 설정을 담은 JSON 형식 파일. OCI Runtime Spec에서 정의한 형식을 따름
- Namespace(네임스페이스): 프로세스가 볼 수 있는 시스템 자원을 격리하는 Linux 커널 기능. pid(프로세스 ID), net(네트워크), mnt(마운트), uts(호스트명), ipc(프로세스 간 통신), user(사용자 ID) 등
- cgroups(Control Groups): 프로세스의 자원(CPU, 메모리 등) 사용량을 제한하는 Linux 커널 기능
번들 디렉터리 생성:
# bundle 디렉터리 생성
# mkdir: Make Directory, 새 디렉터리를 생성하는 명령어
mkdir bundle
루트 파일시스템 준비:
컨테이너 루트 파일시스템을 가져와서 bundle 디렉터리에 저장. 실제 환경에서는 오버레이 파일시스템을 사용해서 이미지에 포함된 여러 레이어를 중첩하지만, 여기서는 docker export를 사용해 단순화된 방식으로 추출
- 오버레이 파일시스템(Overlay Filesystem): 여러 레이어를 겹쳐서 하나의 파일시스템처럼 보이게 하는 기술. 컨테이너 이미지의 레이어를 효율적으로 병합
- 레이어(Layer): 컨테이너 이미지를 구성하는 각각의 파일시스템 변경 단위. Dockerfile의 각 명령어가 새 레이어 생성
레이어 병합 예시:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Layer 3] COPY app/ /app → /app/* 추가
↓
[Layer 2] RUN apt install nginx → /usr/sbin/nginx 추가
↓
[Layer 1] FROM ubuntu → 기본 OS 파일들
→ 최종 rootfs: 모든 레이어가 병합된 결과
# rootfs 디렉터리 생성
# bundle/rootfs: 컨테이너가 루트(/)로 인식할 파일시스템을 저장할 위치
mkdir bundle/rootfs
# Docker Hub에서 alpine 이미지 다운로드
# docker pull: 레지스트리에서 이미지를 로컬로 다운로드
# alpine:3.18: Alpine Linux 3.18 버전 (경량 Linux 배포판, 약 5MB)
docker pull alpine:3.18
# 임시 컨테이너 생성 및 백그라운드 실행
# docker run: 컨테이너 생성 및 실행
# --rm: 컨테이너 종료 시 자동 삭제 (Remove)
# --name tmp: 컨테이너 이름을 'tmp'로 지정
# -d: Detached 모드, 백그라운드에서 실행
# sleep infinity: 무한 대기 (컨테이너가 종료되지 않도록 유지)
docker run --rm --name tmp -d alpine:3.18 sleep infinity
# 컨테이너 파일시스템을 tar로 내보내고 rootfs에 추출
# docker export: 컨테이너의 파일시스템을 tar 아카이브로 내보냄
# - 이미지가 아닌 실행 중인 컨테이너의 현재 상태를 추출
# | (파이프): 앞 명령어의 출력을 뒤 명령어의 입력으로 전달
# tar: 아카이브 처리 명령어
# -x: Extract, 압축 해제
# -C bundle/rootfs: Change directory, 추출할 디렉터리 지정
docker export tmp | tar -xC bundle/rootfs
# 추출된 파일 확인
# ls: List, 디렉터리 내용 표시
ls bundle/rootfs
실행 결과:
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
이 시점에 bundle/rootfs 내부에 컨테이너 루트 파일시스템으로 사용할 파일들이 저장됨
실행 환경 설정 파일 생성:
# runc spec 명령어로 기본 설정 파일 생성
# runc spec: OCI Runtime Spec에 맞는 기본 config.json 생성
# -b bundle: Bundle 디렉터리 경로 지정
# 생성된 config.json은 bundle/config.json에 저장됨
runc spec -b bundle
# 생성된 설정 파일 확인 (JSON 포맷팅)
# cat: Concatenate, 파일 내용 출력
# | jq: JSON 파서, 가독성 좋게 포맷팅 (jq가 없으면 cat만 실행)
cat bundle/config.json | jq
# 번들 디렉터리 구조 확인
# tree: 디렉터리 구조를 트리 형태로 표시
# -L 1: Level 1, 1단계 깊이까지만 표시
tree -L 1 bundle
tree 실행 결과:
bundle
├── config.json ← 컨테이너 실행 환경 설정 파일
└── rootfs ← 컨테이너 루트 파일시스템 디렉터리
1 directory, 1 file
config.json 주요 내용:
{
"ociVersion": "1.0.2-dev", // OCI Runtime Spec 버전
"process": {
// 컨테이너에서 실행할 프로세스 설정
"terminal": true, // 터미널(TTY) 할당 여부
"user": {
// 프로세스 실행 사용자
"uid": 0, // User ID (0 = root)
"gid": 0 // Group ID (0 = root)
},
"args": ["sh"], // 실행할 명령어 (기본: sh 셸)
"env": [
// 환경 변수 목록
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm" // 터미널 타입
],
"cwd": "/" // Current Working Directory
},
"root": {
// 루트 파일시스템 설정
"path": "rootfs", // rootfs 디렉터리 경로
"readonly": true // 읽기 전용 여부
},
"hostname": "runc", // 컨테이너 호스트명
"linux": {
// Linux 전용 설정
"namespaces": [
// 격리할 Namespace 목록
{ "type": "pid" }, // 프로세스 ID 격리
{ "type": "network" }, // 네트워크 격리
{ "type": "ipc" }, // 프로세스 간 통신 격리
{ "type": "uts" }, // 호스트명/도메인명 격리
{ "type": "mount" }, // 마운트 포인트 격리
{ "type": "cgroup" } // cgroups 격리
]
}
}
4.6.2 컨테이너 실행
OCI 표준 조작과 runc 서브 명령어:
생성한 파일시스템 번들로 실제로 컨테이너를 실행. OCI Runtime Specification에 정의된 컨테이너 조작과 이에 대응하는 runc 서브 명령어
OCI 조작과 runc 서브 명령어:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[OCI 표준 조작] [runc 서브 명령어]
─────────────────────────────────────────────
create (생성) → runc create
│ 컨테이너 환경 준비
│ (프로세스는 아직 실행 안 함)
↓
start (시작) → runc start
│ 준비된 컨테이너의 프로세스 시작
↓
kill (종료 신호) → runc kill
│ 컨테이너 프로세스에 신호 전송
↓
delete (삭제) → runc delete
컨테이너 자원 정리 및 삭제
─────────────────────────────────────────────
run (실행) → runc run
create + start를 한 번에 수행
(편의 명령어)
컨테이너 실행 명령어:
고수준 런타임(containerd, CRI-O 등)이 runc를 조작할 때도 이런 서브 명령어를 사용. -b 옵션으로 파일시스템 번들을 지정하면 내부에 저장된 환경 정의 파일(config.json)과 루트 파일시스템(rootfs 디렉터리)을 바탕으로 컨테이너를 생성
# runc run 명령어로 컨테이너 생성 및 실행
# runc run: create와 start를 동시에 수행하는 편의 명령어
# -b bundle: Bundle 디렉터리 경로 지정
# bundle/config.json의 설정과 bundle/rootfs를 사용
# myalpine: 컨테이너 이름 (ID)
# 동일한 이름으로 여러 컨테이너 생성 불가
runc run -b bundle myalpine
실행 결과:
/ # ← 컨테이너 내부 셸 프롬프트
'/'는 현재 위치가 루트 디렉터리
'#'는 root 사용자임을 표시
컨테이너 내부 확인:
컨테이너 내부 셸에서 명령어를 실행하면 호스트와 격리된 환경임을 확인할 수 있음
# 컨테이너 내부에서 OS 정보 확인
# /etc/os-release: 운영체제 정보가 담긴 파일
cat /etc/os-release
실행 결과:
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.18.0
PRETTY_NAME="Alpine Linux v3.18"
# 파일 목록 확인
# ls: 현재 디렉터리의 파일과 디렉터리 표시
ls
실행 결과:
bin etc lib mnt proc run srv tmp var
dev home media opt root sbin sys usr
→ 호스트의 파일시스템이 아닌 컨테이너의 rootfs 내용이 표시됨
# 프로세스 목록 확인
# ps: Process Status, 실행 중인 프로세스 표시
# a: 모든 사용자의 프로세스 표시
# u: 사용자 친화적 형식으로 출력
# x: 터미널에 연결되지 않은 프로세스도 표시
ps aux
실행 결과:
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 ps aux
→ PID 1이 sh임을 주목. 컨테이너 내부에서는 sh가 첫 번째 프로세스(init). 호스트의 수많은 프로세스는 보이지 않음 (PID Namespace 격리)
호스트 vs 컨테이너 프로세스 격리:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[호스트 (Host)] [컨테이너 (Container)]
─────────────────────────────────────────────────
PID 1: systemd PID 1: sh
PID 2: kthreadd PID 7: ps aux
PID 100: sshd
PID 200: dockerd (호스트 프로세스가
PID 300: containerd 전혀 보이지 않음)
PID 500: nginx
...수백 개 프로세스
※ PID Namespace 격리로 컨테이너는 자신의 프로세스만 인식
4.6.3 컨테이너 정지와 삭제
OCI 표준의 kill과 delete 조작:
OCI Runtime Specification에 정의된 정지/삭제 조작도 runc의 서브 명령어로 구현
정지/삭제 조작:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[OCI 조작] [runc 명령어] [설명]
─────────────────────────────────────
kill runc kill 컨테이너 프로세스에 신호(Signal) 전송
delete runc delete 컨테이너 자원 정리 및 삭제
컨테이너 정지:
# 컨테이너에 KILL 신호 전송
# runc kill: 컨테이너 프로세스에 신호 전송
# myalpine: 대상 컨테이너 이름
# KILL: 전송할 신호
# - KILL (SIGKILL, 9): 강제 종료, 무시 불가
# - TERM (SIGTERM, 15): 정상 종료 요청, 프로세스가 무시 가능
# - HUP (SIGHUP, 1): 재시작 요청
runc kill myalpine KILL
Unix/Linux 신호(Signal)란?
- Signal(신호): 프로세스에게 특정 이벤트가 발생했음을 알리는 메커니즘. 프로세스 간 통신의 한 형태
- SIGTERM(15): 정상 종료 요청 신호. 프로세스가 정리 작업 후 종료 가능하며 무시할 수 있음
- SIGKILL(9): 강제 종료 신호. 즉시 프로세스 종료되며 무시 불가, 정리 작업 불가
- SIGHUP(1): 연결 끊김/재시작 요청 신호. 설정 파일 재로드 시 자주 사용
- SIGINT(2): 인터럽트 신호(Ctrl+C). 사용자가 실행 중단 요청
- SIGSTOP(19): 프로세스 일시 정지 신호. 무시 불가
- SIGCONT(18): 정지된 프로세스 재개 신호
컨테이너 삭제:
# 컨테이너 삭제
# runc delete: 컨테이너 자원 정리 및 삭제
# myalpine: 삭제할 컨테이너 이름
# ※ 컨테이너가 정지(stopped) 상태여야 삭제 가능
# ※ 실행 중인 컨테이너를 강제 삭제하려면 --force 옵션 사용
runc delete myalpine
컨테이너 상태 확인:
# 컨테이너 목록 조회
# runc list: 현재 시스템의 모든 runc 컨테이너 표시
runc list
실행 결과 예시:
ID PID STATUS BUNDLE CREATED OWNER
myalpine 12345 running /path/to/bundle 2024-01-15T10:30:00.123456789Z root
4.6.4 컨테이너 생명주기 (Lifecycle)
컨테이너 생명주기 상태도:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
(없음)
│
│ runc create
↓
┌────────┐
│creating│ → 컨테이너 환경 준비 중
└───┬────┘
│ (완료)
↓
┌────────┐
│created │ → 컨테이너 생성됨 (아직 실행 안 됨)
└───┬────┘
│ runc start
↓
┌────────┐
│running │ → 컨테이너 실행 중
└───┬────┘
│ (프로세스 종료 또는 runc kill)
↓
┌────────┐
│stopped │ → 컨테이너 중지됨
└───┬────┘
│ runc delete
↓
(삭제됨)
상태 설명:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
creating : 컨테이너 환경 준비 중
created : 컨테이너 생성 완료, 프로세스 미실행
running : 컨테이너 프로세스 실행 중
stopped : 프로세스 종료됨, 자원 정리 대기
4.6.5 runc 주요 서브 명령어 요약
runc 서브 명령어:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[서브 명령어] [설명] [사용 예]
────────────────────────────────────────────────────────────────
spec 기본 config.json 생성 runc spec -b bundle
create 컨테이너 생성 (프로세스 미실행) runc create -b bundle mycontainer
start 생성된 컨테이너 시작 runc start mycontainer
run 생성과 시작을 동시에 수행 runc run -b bundle mycontainer
list 컨테이너 목록 조회 runc list
state 컨테이너 상태 정보 (JSON) runc state mycontainer
kill 컨테이너에 신호 전송 runc kill mycontainer SIGTERM
delete 컨테이너 삭제 runc delete mycontainer
exec 실행 중인 컨테이너에서 추가 실행 runc exec mycontainer /bin/sh
pause 컨테이너 일시 정지 runc pause mycontainer
resume 일시 정지된 컨테이너 재개 runc resume mycontainer
4.6.6 docker export vs docker save 비교
docker export vs docker save:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[docker export]
│
├─ 대상: 컨테이너
├─ 출력: 단일 레이어 파일시스템
├─ 복원: docker import
├─ 용도: runc용 rootfs 추출, 백업
└─ 히스토리: 손실
[docker save]
│
├─ 대상: 이미지
├─ 출력: 모든 레이어 + 메타데이터
├─ 복원: docker load
├─ 용도: 이미지 배포, 백업
└─ 히스토리: 보존
# docker export: 컨테이너 → tar (단일 레이어)
docker export container_name > container.tar
# docker save: 이미지 → tar (모든 레이어 + 메타데이터)
docker save image_name > image.tar
runc 컨테이너 실행 요약:
핵심 정리:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1] 파일시스템 번들 준비
├─ rootfs 디렉터리: 컨테이너 루트 파일시스템
└─ config.json: 컨테이너 실행 환경 설정
[2] 컨테이너 실행
└─ runc run -b <bundle> <container-name>
[3] 컨테이너 관리
├─ runc list : 컨테이너 목록 확인
├─ runc state : 상태 상세 정보
├─ runc kill : 정지 신호 전송
└─ runc delete : 컨테이너 삭제
※ 고수준 런타임(containerd, CRI-O)은 이러한 runc 명령어를
내부적으로 호출하여 컨테이너를 관리
참고 자료
공식 문서:
- OCI Runtime Spec: https://github.com/opencontainers/runtime-spec
- OCI Image Spec: https://github.com/opencontainers/image-spec
- runc Documentation: https://github.com/opencontainers/runc
도구:
- umoci (OCI 이미지 도구): https://github.com/opencontainers/umoci
- Skopeo: https://github.com/containers/skopeo